/*
	Project:	CoreEngine
	Author:		Dino Bojadzhievski
	File:		Matrix3

	Defines a three-dimensional matrix.
	Non-generic, non-polymorphic.
	Extends nothing.

	Supports minor and cofactor extraction.
*/

#pragma once

#include "../CoreTypes.h"
#include "Matrix2.h"
#include "Vector3.h"
#include <assert.h>

namespace CoreMath
{
	struct Matrix3
	{
		/* Data. */

		real value[3][3];

		/* Constructors. */

		Matrix3()
		{
			this->Identity();
		}

		Matrix3(real fromRawValue[3][3])
		{
			for (byte i = 0; i < 3; i++)
			for (byte j = 0; j < 3; j++)
				this->value[i][j] = fromRawValue[i][j];
		}


		Matrix3(const Matrix2& toCopy)
		{
			for (byte i = 0; i < 3; i++)
			for (byte j = 0; j < 3; j++)
				this->value[i][j] = toCopy.value[i][j];
		}

		/* Operators. */

		inline Matrix3 operator=(const Matrix3& toAssign)
		{
			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					this->value[i][j] = toAssign.value[i][j];
			return *this;
		}

		inline bool operator==(const Matrix3& toCompare) const
		{
			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					if (this->value[i][j] != toCompare.value[i][j])
						return false;
			return true;
		}

		inline bool operator!=(const Matrix3& toCompare) const
		{
			return !(*this == toCompare);
		}

		inline Matrix3 operator+(const Matrix3& toAdd) const
		{
			Matrix3 m_RetVal;
			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					m_RetVal.value[i][j] = this->value[i][j] + toAdd.value[i][j];

			return m_RetVal;
		}

		inline Matrix3 operator-(const Matrix3& toSubtract) const
		{
			Matrix3 m_RetVal;
			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					m_RetVal.value[i][j] = this->value[i][j] - toSubtract.value[i][j];
			return m_RetVal;
		}

		inline Matrix3 operator*(const real& multiplier) const
		{
			Matrix3 m_RetVal;
			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					m_RetVal.value[i][j] = this->value[i][j] * multiplier;
			return m_RetVal;
		}

		inline Vector3 operator*(const Vector3& multiplier) const
		{
			return this->MultiplyByColumnVector(multiplier);
		}

		inline Matrix3 operator*(const Matrix3& multiplier) const
		{
			real rawValue[3][3] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
			Matrix3 m_RetVal(rawValue);
			for (byte i = 0; i < 3; i++)
			for (byte j = 0; j < 3; j++)
			for (byte k = 0; k < 3; k++)
				m_RetVal.value[i][j] = m_RetVal.value[i][j] + (this->value[i][k] * multiplier.value[k][j]);
						//m_RetVal[i][j] = m_RetVal[i][j] + (this->value[i][k] * multiplier.value[k][j];

			return m_RetVal;
		}

		inline Matrix3 operator*=(const real& multiplier)
		{
			*this = *this * multiplier;
			return *this;
		}

		inline Matrix3 operator*=(const Matrix3& multiplier)
		{
			*this = *this * multiplier;
			return *this;
		}

		inline Matrix3 operator-(void) const
		{
			real determinant = this->Determinant();
			if (!determinant) return *this; // Non-invertible matrix detected.
			Matrix3 cofactors = this->Cofactors();
			cofactors.Transpose();
			return cofactors * (1 / determinant);
		}

		/* Common matrix operations implementations.*/

		void Identity(void)
		{
			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					if (i == j)
						this->value[i][j] = 1;
					else
						this->value[i][j] = 0;
		}

		void Transpose(void)
		{
			Matrix3 m_RetVal;
			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					m_RetVal.value[i][j] = this->value[j][i];

			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					this->value[i][j] = m_RetVal.value[i][j];
		}

		inline real Trace(void) const
		{
			real trace = 0;
			for (byte i = 0; i < 3; i++)
				trace += this->value[i][i];
			return trace;
		}

		real Determinant(void) const // Beware! LISP ahead.
		{
			return
				(this->value[0][0] * this->value[1][1] * this->value[2][2]) +
				(this->value[0][1] * this->value[1][2] * this->value[2][0]) +
				(this->value[0][2] * this->value[1][0] * this->value[2][1]) -
				(this->value[0][2] * this->value[1][1] * this->value[2][0]) -
				(this->value[0][0] * this->value[1][2] * this->value[2][1]);
		}

		Matrix2 Minor(const byte row, const byte column) const
		{
			Matrix2 m_RetVal;
			assert(row >= 0 && row < 3);
			assert(column >= 0 && column < 3);
			
			byte colCount = 0;
			byte rowCount = 0;

			for (int i = 0; i < 3; i++)
			{
				if (i != row)
				{
					colCount = 0;
					for (int j = 0; j < 3; j++)
					{
						// when j is not the element
						if (j != column)
						{
							m_RetVal.value[rowCount][colCount] = this->value[i][j];
							colCount++;
						}
					}
					rowCount++;
				}
			}
			return m_RetVal;
		}

		Matrix3  Minors(void) const
		{
			Matrix3 m_RetVal;
			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					m_RetVal.value[i][j] = this->Minor(i, j).Determinant();
				return m_RetVal;
		}

		Matrix3 Cofactors(void) const
		{
			Matrix3 m_RetVal = this->Minors();
			for (byte i = 0; i < 3; i++)
				for (byte j = 0; j < 3; j++)
					if (i + j != 0)
					m_RetVal.value[i][j] *= -1;
		}

		inline Vector3 MultiplyByColumnVector(const Vector3& multiplier) const
		{
			real i, j, k;
			i = this->value[0][0] * multiplier.i + this->value[0][1] * multiplier.j + this->value[0][2] * multiplier.k;
			j = this->value[1][0] * multiplier.i + this->value[1][1] * multiplier.j + this->value[1][2] * multiplier.k;
			k = this->value[2][0] * multiplier.i + this->value[2][1] * multiplier.j + this->value[2][2] * multiplier.k;

			return Vector3(i, j, k);
		}
	};
}